#ifndef RS_TABLE_H
#define RS_TABLE_H
#include <vector>
#include <hash_map>
#include "rs_stream.h"
#include "rs_string.h"
#include "rs_log.h"
using namespace std;


enum rs_type { // types of data in table
    rt_none,
    rt_text2, // rs_string, 2 bytes per character
    rt_numb4,  // unsigned __int32, 4 bytes per number
};

static const int rst_max_col=15;
    // at most so many columns can be in table

class rs_rix {
    // table row indexes
private:
    unsigned index;
public:
    static const int _none= static_cast<unsigned int>(-1);
    static const rs_rix begin() { return rs_rix(0); }
    static const rs_rix none() { return rs_rix(_none); }
    operator unsigned () const { return index; };
    explicit rs_rix (unsigned ix) : index (ix) {};
    rs_rix () : index (_none) {}; // value is meant as 'none'
    bool is_valid() const { return index != _none; };
    void reset () { index = _none; };
    rs_rix operator ++() { ++index; return rs_rix(index); }
    rs_rix operator ++(int) { unsigned value=index++; return rs_rix(value); }
};

class any_cix {
    // used for indexes that refer to any column
private:
    unsigned index;
public:
    static const int none=rst_max_col;
    static const any_cix begin() { return any_cix(0); }
    operator unsigned () const { return index; };
    explicit any_cix (unsigned ix) : index (ix) {};
    any_cix () : index (rst_max_col) {}; // value is meant as 'none'
    bool is_valid() const { return index != none; };
    void reset () { index = none; };
    any_cix operator ++() { ++index; return any_cix(index); }
    any_cix operator ++(int) { unsigned value=index++; return any_cix(value); }
};

class text_cix {
    // used for indexes that refer to textual columns
private:
    unsigned index;
public:
    static const int none=rst_max_col;
    static const text_cix begin() { return text_cix(0); }
    operator unsigned() const { return index; };
    operator any_cix() const { return any_cix(index); };
    explicit text_cix (unsigned ix) : index (ix) {};
    text_cix () : index (rst_max_col) {}; // value is meant as 'none'
    bool is_valid() const { return index != none; };
    void reset () { index = none; };
};

class numb_cix {
    // used for indexes that refer to numerical columns
private:
    unsigned index;
public:
    static const int none=rst_max_col;
    operator unsigned () const { return index; };
    operator any_cix() const { return any_cix(index); };
    explicit numb_cix (unsigned ix) : index (ix) {};
    numb_cix () : index (rst_max_col) {}; // value is meant as 'none'
    bool is_valid() const { return index != none; };
    void reset () { index = none; };
};


// textual column
typedef pair <rs_string, unsigned> text_pair;
typedef stdext::hash_multimap<rs_string, unsigned> text_hash;
struct rs_column {
    rs_column() {
        rsc_type = rt_none;
        rsc_hashed = false;
        rsc_name = L"";
        rsc_min_width = 0;
    }
    vector<rs_rix> rs_column::get_text_row_all (const rs_string& text) const;
    unsigned get_text_row_count (const rs_string& text) const;
    rs_rix get_text_row_any (const rs_string& text, bool& several) const;
// fields
    // one column in rs_table, either textual or numeric
    rs_type  rsc_type;
    bool     rsc_hashed; // false unless rex_type==rt_text2
    // if true, data in column can be found fast with hash
    rs_string  rsc_name;
    unsigned rsc_min_width; // minimal width when storing
    rs_string_v rsc_text; // texts/words in the table
    vector<unsigned> rsc_numb; // numbers in the table
    text_hash rsc_hash; // hash to find text/word fast
};


class rs_table {
    // table with linguistic data: dictionary and composition rules
    // rs_table is a 2-dimensional array of texts and numbers
    // every column can be e.g. 
    // - words in Traditional Orthography (TO)
    // - words in reformed orthography (RO)
    // - pronunciation in some sistem like IPA or CMU
    // - additional data about words (flagS/features): noun, verb, past tense, ...
    // - word frequencies
    // texts/numbers in several columns with the same row index are related
public:
    rs_table (const rs_string& rst_fname, bool update_hashes=true) {
        read (rst_fname, update_hashes);
    }
    rs_table () { init(); }
    
    rs_table (
        const rs_string& a_abbr,
        const rs_string& a_name,
        const rs_string& a_tag)
    {
        init();
        set_abbr_name_tag (a_abbr, a_name, a_tag);
    }
    bool is_valid() { return rst_valid; };
    void create_hashes ();
    void reserve (size_t n);
    bool write (
        const rs_string& rst_fname,
        const rs_string& columns=L"",
        const rs_string& conditions=L"",
        const rs_string& sort_order_str=L"",
        rs_rix max_row_index=rs_rix(rs_rix::none()),
        bool native=true);
    bool export_table (
        const rs_string& rst_fname,
        const rs_string& columns=L"",
        const rs_string& conditions=L"",
        const rs_string& sort_order_str=L"",
        rs_rix max_row_index=rs_rix(rs_rix::none())) {
            return write (rst_fname, columns, conditions,
                sort_order_str, max_row_index, false);
    }
    bool read_line (const rs_string& input_line, bool update_hashes, bool last_all);
    bool read (const rs_string& rst_fname, bool update_hashes=true);
    bool import_table (const rs_string& in_fname, const rs_string& skip_prefix, bool update_hashes);

    void clear ();
    bool get_columns_from (const rs_table& source);
    bool join_table (const rs_table& rst_add, const rs_string& link, const rs_string& columns);
    bool add_table_rows (const rs_table& source, const rs_rix& begin, const rs_rix& end, unsigned col_count);
    bool add_all_rows (const rs_table& source, unsigned col_count)
    {   // adds all rows from from table 'source' of the same columns
        return add_table_rows (source, rs_rix::begin(), rs_rix::none(), col_count);
    }
    bool add_one_row (const rs_table& source, const rs_rix& rix, unsigned col_count)
    {   // adds a single row from from table 'source' of the same columns
        return add_table_rows (source, rix, rs_rix(rix+1), col_count);
    }
    void add_empty_rows (unsigned row_count);
    vector<rs_rix> rs_table::get_text_row_all (const rs_string& text, text_cix cix) const
    {
        return rst_table[cix].get_text_row_all (text);
    };
    unsigned rs_table::get_text_row_count (const rs_string& text, text_cix cix) const
    {
        return rst_table[cix].get_text_row_count (text);
    };
    rs_rix get_text_row_any (const rs_string& text, text_cix cix) const
    {
        bool several;
        return get_text_row_any (text, cix, several);
    }
    rs_rix get_text_row_any (const rs_string& text, text_cix cix, bool& several) const
    {
        return rst_table[cix].get_text_row_any (text, several);
    };
    rs_rix get_text_row_freq (
        const rs_string& text,
        text_cix search_cix,
        numb_cix freq_cix,
        bool& several) const;
    bool translate_one (
        const rs_string &src,
        text_cix src_cix,
        numb_cix freq_cix,
        text_cix cnv_cix,
        rs_string &cnv,
        bool& several) 
        const;
    text_cix add_text_col (const rs_string& column_name, bool hashed, unsigned min_width, bool allow_existing=false) {
        any_cix cix  = add_any_col (column_name, rt_text2, hashed, min_width, allow_existing);
        return text_cix (cix);
    }
    numb_cix add_numb_col (const rs_string& column_name, unsigned min_width, bool allow_existing=false) {
        any_cix cix  = add_any_col (column_name, rt_numb4, false, min_width, allow_existing);
        return numb_cix (cix);
    }
    const rs_column& get_column_ref (any_cix cix) const {
        return rst_table[cix];
    }
    bool add_columns (const rs_string& columns);
    bool change_case (const rs_string& columns, bool to_uppercase);
    rs_string_v& get_text_col (text_cix cix) {
        if (cix >= rst_data_cols) {
            rs_report_error (L"Requesting non-existing column: ", rs_stringed (cix));
            cix = text_cix::begin();
        }
        return rst_table[cix].rsc_text;
    }
    text_cix get_text_cix (const rs_string& col_name) const {
        return get_text_cix (get_any_cix (col_name));
    };
    numb_cix get_numb_cix (const rs_string& col_name) const {
        return get_numb_cix (get_any_cix (col_name));
    };
    text_cix get_text_cix (any_cix column) const {
        if (column.is_valid() && rst_table[column].rsc_type == rt_text2)
            return text_cix (column);
        else
            return text_cix (text_cix::none);
    };
    numb_cix get_numb_cix (any_cix column) const {
        if (column.is_valid() && rst_table[column].rsc_type == rt_numb4)
            return numb_cix (column);
        else
            return numb_cix (numb_cix::none);
    };
    rs_string get_col_name (any_cix cix) const {
        return rst_table[cix].rsc_name;
    }
    rs_type get_col_type (any_cix cix) const {
        return rst_table[cix].rsc_type;
    }
    bool get_col_hash (any_cix cix) const {
        return rst_table[cix].rsc_hashed;
    }
    void set_col_hash (any_cix cix, bool hash) {
        rst_table[cix].rsc_hashed = hash;
    }
    const rs_string& get_text (text_cix cix, rs_rix rix) const { 
        return rst_table[cix].rsc_text[rix];
    };
    unsigned get_numb (numb_cix cix, rs_rix rix) const {
        return rst_table[cix].rsc_numb[rix];
    };
    void set_text (const rs_string& text, text_cix cix, rs_rix rix) {
        rst_table[cix].rsc_text[rix]=text;
    };
    void set_numb (unsigned numb, numb_cix cix, rs_rix rix) {
        rst_table[cix].rsc_numb[rix]=numb;
    };
    unsigned inc_numb (numb_cix cix, rs_rix rix);
    unsigned incr_numb (unsigned numb, numb_cix cix, rs_rix rix);
    rs_rix append_text (const rs_string& text, const text_cix cix, bool update_hashes=true);
    rs_rix append_numb (unsigned numb, const numb_cix cix);

    void set_numbered (bool value) { rst_numbered = value; } ;
    void set_col_min_width (unsigned min_width, any_cix cix) { rst_table[cix].rsc_min_width = min_width; } ;
    unsigned get_col_min_width (any_cix cix) const { return rst_table[cix].rsc_min_width; } ;
    rs_rix get_row_count() const {return rs_rix(rst_data_rows); };
    any_cix get_col_count() const {return any_cix(unsigned(rst_data_cols)); };
    void set_abbr_name_tag (
        const rs_string& a_abbr,
        const rs_string& a_name,
        const rs_string& a_tag)
    {
        rst_abbr = a_abbr;
        rst_name = a_name;
        rst_tag = a_tag;
    }
    rs_string get_abbr() const { return rst_abbr; };
    void set_abbr(const rs_string& value) { rst_abbr = value; };
    rs_string get_tag() const { return rst_tag; };
    void set_tag(const rs_string& value) { rst_tag = value; };
    void get_any_cix (const rs_string&  col_name, any_cix& cix, rs_type& col_type) const;
    any_cix get_any_cix (const rs_string&  col_name) const;
private:
// member functions
    void init () {
        rst_valid = true;
        rst_data_cols = 0;
        rst_data_rows = 0;
        rst_numbered = true;
    };
    bool read_one (const rs_string& rst_fname, bool update_hashes=true);
    any_cix add_any_col (
        const rs_string& column_name,
        rs_type col_type,
        bool hashed,
        unsigned min_width=0,
        bool allow_existing=false);
    bool update_table (const rs_table& rst_add, const rs_string& link, const rs_string& columns);
    bool compatible_columns (const rs_table& other, unsigned col_count) const;
    bool parse_column_list (const rs_string& columns, vector<any_cix>& col_vec) const;
    bool parse_and_sort_column (
        const rs_string& sort_order_str,
        bool& sorting_present,
        vector<unsigned>& row_order);
    void parse_and_evaluate_conditions (
        const rs_string& conditions,
        bool& cond_present,
        vector<bool>& out_row_vec,
        unsigned& out_row_cnt);
    bool check_var (const rs_string& line, const rs_string& var_name, rs_string& var_value);
    bool check_var (const rs_string& line, const rs_string& var_name, size_t& var_value);
// variables
    bool rst_valid;
    rs_string rst_abbr;  // 2 or more ASCII characters
    rs_string rst_name;  // any unicode string
    rs_string rst_tag;   // lanugage tag, rfc 4646, e.g. "en-x-cutspell"
    bool rst_numbered;  // flag: every line in file has its row number
    unsigned rst_data_cols;  // number of columns, i.e. number of non-empty columns in rst_table
    unsigned rst_data_rows;
    rs_column rst_table[rst_max_col];
    // todo: add field for origin of data: package, URL, copyright, --tselect
    // todo: extend table properties like tag, abbr, ... to vector of string pairs
};


#endif
